home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
pcl
/
cl-nd-cl.lha
/
clue
/
clio
/
scroll-frame.lisp
< prev
next >
Wrap
Lisp/Scheme
|
1990-07-19
|
22KB
|
647 lines
;;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Syntax:Common-Lisp -*-
;;;----------------------------------------------------------------------------------+
;;; |
;;; TEXAS INSTRUMENTS INCORPORATED |
;;; P.O. BOX 149149 |
;;; AUSTIN, TEXAS 78714 |
;;; |
;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
;;; |
;;; Permission is granted to any individual or institution to use, copy, modify, and |
;;; distribute this software, provided that this complete copyright and permission |
;;; notice is maintained, intact, in all copies and supporting documentation. |
;;; |
;;; Texas Instruments Incorporated provides this software "as is" without express or |
;;; implied warranty. |
;;; |
;;;----------------------------------------------------------------------------------+
(in-package "CLIO-OPEN")
(export '(scroll-frame
make-scroll-frame
scroll-frame-area
scroll-frame-content
scroll-frame-horizontal
scroll-frame-left
scroll-frame-position
scroll-frame-reposition
scroll-frame-top
scroll-frame-vertical
))
(defcontact scroll-frame (core composite)
((horizontal :type switch
:initform :on
:initarg :horizontal
:reader scroll-frame-horizontal) ; setf defined below
(left :type integer
:initform 0
:initarg :left
:accessor scroll-frame-left)
(top :type integer
:initform 0
:initarg :top
:accessor scroll-frame-top)
(vertical :type switch
:initform :on
:initarg :vertical
:reader scroll-frame-vertical)) ; setf defined below
(:resources
(border-width :initform 0)
(content :type (or function list) :initform nil)
horizontal
left
top
vertical)
(:documentation
"Provide horizontal and/or vertical scrolling controls for an arbitrary content contact"))
;;;----------------------------------------------------------------------------+
;;; |
;;; Accessors |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod scroll-frame-content ((scroll-frame scroll-frame))
(first (composite-children (scroll-frame-area scroll-frame))))
(proclaim '(inline scroll-frame-hscroller))
(defun scroll-frame-hscroller (scroll-frame)
(with-slots (children) scroll-frame
(find :hscroller children
:key #'contact-name
:test #'eq)))
(proclaim '(inline scroll-frame-vscroller))
(defun scroll-frame-vscroller (scroll-frame)
(with-slots (children) scroll-frame
(find :vscroller children
:key #'contact-name
:test #'eq)))
(defmethod scroll-frame-area ((scroll-frame scroll-frame))
(with-slots (children) scroll-frame
(find :scroll-area children
:key #'contact-name
:test #'eq)))
(defmethod (setf contact-foreground) :after (value (self scroll-frame))
(declare (ignore value))
(with-slots (foreground) self
(let ((hscroller (scroll-frame-hscroller self))
(vscroller (scroll-frame-vscroller self)))
(when hscroller
(setf (contact-foreground hscroller) foreground))
(when vscroller
(setf (contact-foreground vscroller) foreground)))
(setf (window-border (scroll-frame-area self)) foreground)))
(defmethod (setf scroll-frame-vertical) (value (self scroll-frame))
(with-slots (foreground top vertical) self
(setf vertical value)
(let ((vscroller (scroll-frame-vscroller self))
(content (scroll-frame-content self)))
(ecase value
(:on
(if vscroller
;; Map existing scroller
(setf (contact-state vscroller) :mapped)
(progn
;; Create a new scroller
(setf vscroller (make-scroller :parent self
:name :vscroller
:foreground foreground
:border-width 0
:orientation :vertical))
;; Program scroller to scroll content
(add-callback vscroller :new-value
#'(lambda (new-top scroll-frame)
(with-slots (left top) scroll-frame
(unless (= new-top top)
(sf-scroll-to
scroll-frame
left
(setf top new-top)))))
self)))
;; Calibrate scroller with current content
(when content
(sf-vertical-calibrate
content vscroller top (contact-height (scroll-frame-area self)))))
(:off
(when vscroller
(setf (contact-state vscroller) :withdrawn)))))
value))
(defmethod (setf scroll-frame-horizontal) (value (self scroll-frame))
(with-slots (foreground left horizontal) self
(setf horizontal value)
(let ((hscroller (scroll-frame-hscroller self))
(content (scroll-frame-content self)))
(ecase value
(:on
(if hscroller
;; Map existing scroller
(setf (contact-state hscroller) :mapped)
(progn
;; Create a new scroller
(setf hscroller (make-scroller :parent self
:name :hscroller
:foreground foreground
:border-width 0
:orientation :horizontal))
;; Program scroller to scroll content
(add-callback hscroller :new-value
#'(lambda (new-left scroll-frame)
(with-slots (left top) scroll-frame
(unless (= new-left left)
(sf-scroll-to
scroll-frame
(setf left new-left)
top))))
self)))
;; Calibrate scroller with current content
(when content
(sf-horizontal-calibrate
content hscroller left (contact-width (scroll-frame-area self)))))
(:off
(when hscroller
(setf (contact-state hscroller) :withdrawn)))))
value))
(defmethod scroll-frame-position ((self scroll-frame))
(with-slots (left top) self
(values left top)))
(defmethod scroll-frame-reposition ((self scroll-frame) &key left top)
"Changes the horizontal/vertical position of the content (in content
units) which appears at the left/top edge of the scroll-frame. The
final content position (possibly adjusted via :horizontal-adjust and
:vertical-adjust callbacks) is returned."
(with-slots ((current-left left) (current-top top) vertical horizontal) self
(let*
((content (scroll-frame-content self))
(left-changed-p (and
left
(/= (setf left (apply-callback-else (content :horizontal-adjust left) left))
current-left)))
(top-changed-p (and
top
(/= (setf top (apply-callback-else (content :vertical-adjust top) top))
current-top))))
(when left-changed-p
(setf current-left left)
(when (eq :on horizontal)
(setf (scale-value (scroll-frame-hscroller self)) current-left)))
(when top-changed-p
(setf current-top top)
(when (eq :on vertical)
(setf (scale-value (scroll-frame-vscroller self)) current-top)))
(when (or left-changed-p top-changed-p)
;; Redisplay content at new position
(sf-scroll-to self current-left current-top))
(values current-left current-top))))
(defun sf-scroll-to (scroll-frame left top)
(let ((content (scroll-frame-content scroll-frame)))
(when content
(apply-callback-else (content :scroll-to left top)
;; Default scrolling by moving content window w.r.t area.
;; Content units are n pixels, where n is determined from
;; pixels-per-unit used to calibrate scroller indicator size.
(let ((hscroller (scroll-frame-hscroller scroll-frame))
(vscroller (scroll-frame-vscroller scroll-frame))
(area (scroll-frame-area scroll-frame)))
(with-state (content)
(move content
(- (pixel-round (if hscroller
(/ (* left (contact-width area))
(scale-indicator-size hscroller))
left)))
(- (pixel-round (if vscroller
(/ (* top (contact-height area))
(scale-indicator-size vscroller))
top))))))))))
(defun sf-horizontal-calibrate (content hscroller left width)
;; Program scroller to adjust value
(add-callback hscroller :adjust-value
#'(lambda (value content)
(or (when content
(apply-callback content :horizontal-adjust value))
value))
content)
;; Update scroller values
(multiple-value-bind (min max ppu)
(apply-callback-else (content :horizontal-calibrate)
(values 0 (max 0 (- (contact-width content) width)) 1))
;; Clamp current left to new range
(let ((value (min max left)))
(scale-update hscroller
:value value
:minimum min
:maximum max
:indicator-size (/ width ppu)
:increment 1)
;; Returned clamped value
value)))
(defun sf-vertical-calibrate (content vscroller top height)
;; Program scroller to adjust value
(add-callback vscroller :adjust-value
#'(lambda (value content)
(or (when content
(apply-callback content :vertical-adjust value))
value))
content)
;; Update scroller values
(multiple-value-bind (min max ppu)
(apply-callback-else (content :vertical-calibrate)
(values 0 (max 0 (- (contact-height content) height)) 1))
(let ((value (min max top)))
(scale-update vscroller
:value value
:minimum min
:maximum max
:indicator-size (/ height ppu)
:increment 1)
;; Return clamped value
value)))
;;;----------------------------------------------------------------------------+
;;; |
;;; Geometry Management |
;;; |
;;;----------------------------------------------------------------------------+
(defmethod change-layout ((self scroll-frame) &optional newly-managed)
(declare (ignore newly-managed))
(with-slots (width height horizontal vertical) self
;; Is initial scroll-frame size still undefined?
(if (unless (realized-p self) (or (zerop width) (zerop height)))
;; Yes, change to valid initial size (this invokes change-layout again)
(multiple-value-bind (preferred-width preferred-height)
(preferred-size self)
(change-geometry
self :width preferred-width :height preferred-height :accept-p t))
;; No, update layout for valid size.
(let*
((hscroller (when (eq :on horizontal) (scroll-frame-hscroller self)))
(vscroller (when (eq :on vertical) (scroll-frame-vscroller self)))
(area (scroll-frame-area self))
(hheight (if hscroller (contact-height hscroller) 0))
(vwidth (if vscroller (contact-width vscroller) 0))
(hwidth (max 0 (- width vwidth)))
(vheight (max 0 (- height hheight)))
(abw (* 2 (contact-border-width area))))
;; Lay out scrollers
(when hscroller
(with-state (hscroller)
(resize hscroller hwidth hheight 0)
(move hscroller 0 (- height hheight))))
(when vscroller
(with-state (vscroller)
(resize vscroller vwidth vheight 0)
(move vscroller (- width vwidth) 0)))
;; Layout scroll area
(with-state (area)
(resize area
(max 0 (- width vwidth abw))
(max 0 (- height hheight abw))
(contact-border-width area))
(move area 0 0))
))))
(defmethod manage-geometry ((self scroll-frame) child x y width height border-width &key)
(case (contact-name child)
(:scroll-area
;; Approve if total outside size/position remains unchanged.
(let* ((approved-bw (or border-width (contact-border-width child)))
(delta-bw (* 2 (- (contact-border-width child) approved-bw)))
(approved-x 0)
(approved-y 0)
(approved-width (+ (contact-width child) delta-bw))
(approved-height (+ (contact-height child) delta-bw)))
(values
(when
;; Change approved?
(and
(or (null x) (= x approved-x))
(or (null y) (= y approved-y))
(or (null width) (= width approved-width))
(or (null height) (= height approved-height))
(= border-width approved-bw))
;; Yes, update layout if change is performed
'change-layout)
approved-x
approved-y
approved-width
approved-height
approved-bw)))
(otherwise
;; Approve any scroller size change. This should happen only during rescale.
(values
(when
(and
(or (null border-width) (= border-width (contact-border-width child)))
(or (null x) (= x (contact-x child)))
(or (null y) (= y (contact-y child))))
'change-layout)
(contact-x child)
(contact-y child)
(or width (contact-width child))
(or height (contact-height child))
(contact-border-width child)))))
(defmethod preferred-size ((self scroll-frame) &key width height border-width)
(with-slots ((self-width width) (self-height height) (self-border-width border-width)) self
(let ((suggested-width (or width self-width))
(suggested-height (or height self-height))
(suggested-border-width (or border-width self-border-width)))
(values
(if (plusp suggested-width)
suggested-width
(let ((content (scroll-frame-content self))
(hscroller (scroll-frame-hscroller self))
(vscroller (scroll-frame-vscroller self)))
(+ (max (if content (contact-width content) 0)
(if hscroller (contact-width hscroller) 0))
(if vscroller (contact-width vscroller) 0))))
(if (plusp suggested-height)
suggested-height
(let ((content (scroll-frame-content self))
(hscroller (scroll-frame-hscroller self))
(vscroller (scroll-frame-vscroller self)))
(+ (max (if content (contact-height content) 0)
(if vscroller (contact-height vscroller) 0))
(if hscroller (contact-height hscroller) 0))))
suggested-border-width ))))
(defmethod resize :after ((self scroll-frame) new-width new-height new-border-width)
(declare (ignore new-width new-height new-border-width))
(change-layout self))
(defmethod add-child :before ((self scroll-frame) child &key)
(assert (member (contact-name child) '(:hscroller :vscroller :scroll-area) :test #'eq) ()
"A scroll-frame does not allow you to define new children."))
;;;----------------------------------------------------------------------------+
;;; |
;;; Initialization |
;;; |
;;;----------------------------------------------------------------------------+
(defun make-scroll-frame (&rest initargs)
(apply #'make-contact 'scroll-frame initargs))
(defmethod initialize-instance :after ((self scroll-frame) &key content &allow-other-keys)
(with-slots (foreground vertical horizontal) self
(let (;; Create scroll area
(area (make-contact 'scroll-area
:parent self
:name :scroll-area
:border-width 1
:border foreground)))
;; Create content, if given.
(when content
(multiple-value-bind (content-constructor content-initargs)
(etypecase content
(function content)
(list (values (first content) (rest content))))
(apply content-constructor
:name (or (getf content-initargs :name) :content)
:parent area
content-initargs))))
;; Initialize scroll bars
(setf (scroll-frame-horizontal self) horizontal)
(setf (scroll-frame-vertical self) vertical)))
;;;----------------------------------------------------------------------------+
;;; |
;;; Scroll Area |
;;; |
;;;----------------------------------------------------------------------------+
(defcontact scroll-area (composite) ()
(:documentation "Geometry manager for the scroll area of a scroll frame."))
;;; Geometry management policy:
;;; 1. Content border width forced to 0. This prevents the bottom/right edges
;;; of a small content from intruding.
;;; 2. Content size and position is unrestricted.
;;; 3. Only one content child allowed.
(defmethod add-child :before ((self scroll-area) child &key)
(declare (ignore child))
(assert (not (composite-children self)) ()
"A scroll area can have only one child."))
(defmethod change-layout ((self scroll-area) &optional newly-managed)
(declare (ignore newly-managed))
(with-slots (children (scroll-frame parent) width height) self
(let ((content (first children)))
(when content
;; If realized, then recalibrate scrollers for new content
;; (otherwise, not necessary since initial calibration will be done
;; after initial scroll-area size is set).
(when (realized-p self)
(sf-recalibrate scroll-frame))
;; Define content callbacks used by application to report new calibration data
(flet
((horizontal-update
(&key position minimum maximum pixels-per-unit scroll-frame)
;; Recalibrate scroller, if necessary
(when (eq :on (scroll-frame-horizontal scroll-frame))
(scale-update
(scroll-frame-hscroller scroll-frame)
:value position
:minimum minimum
:maximum maximum
:indicator-size (when pixels-per-unit
(/ (contact-width (scroll-frame-area scroll-frame))
pixels-per-unit))))
;; Update current scroll-frame position
(when position
(with-slots (left top) scroll-frame
(sf-scroll-to
scroll-frame
(setf left position)
top))))
(vertical-update
(&key position minimum maximum pixels-per-unit scroll-frame)
;; Recalibrate scroller, if necessary
(when (eq :on (scroll-frame-vertical scroll-frame))
(scale-update
(scroll-frame-vscroller scroll-frame)
:value position
:minimum minimum
:maximum maximum
:indicator-size (when pixels-per-unit
(/ (contact-height (scroll-frame-area scroll-frame))
pixels-per-unit))))
;; Update current scroll-frame position
(when position
(with-slots (left top) scroll-frame
(sf-scroll-to
scroll-frame
left
(setf top position))))))
(add-callback content :horizontal-update
#'horizontal-update
:scroll-frame scroll-frame)
(add-callback content :vertical-update
#'vertical-update
:scroll-frame scroll-frame))
;; Initialize content
(with-state (content)
;; Initialize content position (this may be changed later if
;; default pixel scrolling is used)
(move content 0 0)
;; Force content border width to 0
(with-slots
((content-width width) (content-height height)) content
(resize content content-width content-height 0)))))))
(defmethod manage-geometry ((self scroll-area) content x y width height border-width &key)
(flet
((update-scroller-maximum
(scroll-area)
;; Called when an approved content geometry change is performed. When default
;; scrolling is used, then scrollers must be updated to reflect new
;; pixel size of content w.r.t scroll-area.
(let ((content (first (composite-children scroll-area))))
;; Default scrolling?
(unless (callback-p content :scroll-to)
(let
((frame (contact-parent scroll-area))
(max-h (max 0 (- (contact-width content) (contact-width scroll-area))))
(max-v (max 0 (- (contact-height content) (contact-height scroll-area)))))
(apply-callback
content :horizontal-update
:maximum max-h
:position (min (scroll-frame-left frame) max-h))
(apply-callback
content :vertical-update
:maximum max-v
:position (min (scroll-frame-top frame) max-v)))))))
(values
(when (or (null border-width) (= border-width 0))
#'update-scroller-maximum)
(or x (contact-x content))
(or y (contact-y content))
(or width (contact-width content))
(or height (contact-height content))
0)))
(defmethod resize :after ((self scroll-area) new-width new-height new-bw)
(declare (ignore new-width new-height new-bw))
(with-slots (parent) self
(let ((scroll-frame parent))
(sf-recalibrate scroll-frame)
(unless (realized-p self)
;; Move content into initial position, now that content units have been
;; defined.
(sf-scroll-to
scroll-frame
(scroll-frame-left scroll-frame)
(scroll-frame-top scroll-frame))))))
(defun sf-recalibrate (scroll-frame)
(let ((content (scroll-frame-content scroll-frame)))
(when content
(with-slots (left top horizontal vertical) scroll-frame
(with-slots (width height) (scroll-frame-area scroll-frame)
(let ((new-left left) (new-top top))
(let ((hscroller (when (eq :on horizontal) (scroll-frame-hscroller scroll-frame))))
(when hscroller
(setf new-left (sf-horizontal-calibrate content hscroller left width))))
(let ((vscroller (when (eq :on vertical) (scroll-frame-vscroller scroll-frame))))
(when vscroller
(setf new-top (sf-vertical-calibrate content vscroller top height))))
(scroll-frame-reposition scroll-frame :left new-left :top new-top)))))))